/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-04
 * V4.0
 */
package com.jphenix.driver.propertie.instanceb;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.jphenix.driver.nodehandler.FNodeHandler;
import com.jphenix.kernel.classloader.ResourcesLoader;
import com.jphenix.kernel.objectloader.exception.BeanException;
import com.jphenix.kernel.objectloader.instanceb.BaseBean;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.viewhandler.INodeHandler;
import com.jphenix.standard.viewhandler.IViewHandler;

/**
 * XML格式的 配置文件处理类
 * 
 * getParameter 方法中的传入参数 key 
 * 
 * 支持 sub1/sub2/sub3/key 格式
 * 
 * 对应的xml格式为:
 * 
 *   <root>
 *   		<sub1>
 *   				<sub2>
 *   						<sub3>
 *   								<key>value</key>
 *   						</sub3>
 *   				</sub2>
 *   		</sub1>
 *   </root>
 * 
 * 配置文件中还支持引用外部文件，引用的文件作为该配置文件处理类的子类
 * 
 * 为什么引用的文件内容不能放在当前处理类中？因为涉及到信息保存，一个类对应一个配置文件。
 * <root>
 * 		<import id="子类主键">文件相对路径</import>
 * 
 *      <import>文件相对路径</import>  <!-- 子配置文件中的参数都归于当前配置文件对象中 -->
 * </root>
 * 
 * 注意： 引用外部文件标签只能用在文件的根节点内。
 * 比如： 上面的例子。
 * 
 * 不允许：
 * 
 *        <root>
 *        		<sub1>
 *        				<import id="子类主键">不允许这么写，必须写在根节点中</import>
 *        		</sub1>
 *        </root>
 * 
 * 17-03-28
 * 原本类中带保存配置文件的方法的，几乎从来没用过。
 * 增加了同一个类引用多个配置文件到当前类
 * 
 * 2018-11-08 解决了配置文件发生变化后，无法自动重新加载内容的问题。
 * 2019-01-24 重构了资源加载器，修改了加载器类路径
 * 2020-08-06 去掉了配置文件处理接口，改为实体类
 * 2021-09-12 完善了代码，避免了死循环错误
 * 2022-10-20 重构了代码
 * 2022-10-21 修改了发现的错误
 * 2022-11-27 增加了获取核心xml对象方法，主要用于调试
 * 
 * @author 刘虻
 * 2012-11-5 上午10:43:14
 */
@ClassInfo({"2022-11-27 23:03","XML格式的 配置文件处理类"})
public class XmlConfProp extends BaseBean {

	//#region 【变量声明区】
	protected IViewHandler                confXml            = FNodeHandler.newNodeHandler(); // 配置文件核心类
	protected List<Object>                confSourceList     = new ArrayList<>();             // 依次加载的配置文件路径或XML对象
	protected Map<String,Long>            confFileTimeMap    = new HashMap<>();               // 依次加载的配置文件最后更新时间对照容器 key：文件路径 value：最后更新时间
	//#endregion
	
	//#region 【构造函数】
	/**
	 * 构造函数
	 * @author 刘虻
	 */
	public XmlConfProp() {
		super();
	}
	//#endregion

	//#region getParameter 获取指定配置文件参数值
	/**
	 * 获取指定配置文件参数值
	 * @param key 参数主键
	 * @return    参数值
	 * 2022年10月20日
	 * @author mbg
	 */
    public String getParameter(String key) {
		int point = key.indexOf("/"); //参数分割点
		if(point==0) {
			key = key.substring(1); // 去掉开头的斜杠，如果存在的话
			point = key.indexOf("/");
		}
		IViewHandler xml    = confXml; // 初始参数容器
		String       subKey = key;     // 参数段值
		while(point>0) {
			// 分级参数，即： key1/key2/key3
			subKey = key.substring(0,point); //获取配置信息主键
			key = key.substring(point+1);  //处理剩下的主键信息
			xml = xml.getFirstChildNodeByNodeName(subKey);
			point = key.indexOf("/");
		}
		return  getFixParameter(xml.getFirstChildNodeByNodeName(key).getNodeText());
	}
    //#endregion

	//#region reload 重新加载配置文件中的参数
	/**
	 * 重新加载配置文件中的参数
	 * @return            当前类实例
	 * @throws Exception  异常
	 * 2022年10月20日
	 * @author mbg
	 */
    public XmlConfProp reload() throws Exception {
		if(confSourceList.size()<1) {
			return this;
		}
		confXml.clear();
		INodeHandler nh;
		for(Object ele:confSourceList) {
			if(ele instanceof String) {
				nh = createViewHandler((String)ele);
				parsePropertys(nh,nh.getFilePath());
			}else if(ele instanceof INodeHandler) {
				parsePropertys((INodeHandler)ele,((INodeHandler)ele).getFilePath());
			}else {
				parsePropertys((IViewHandler)ele,null);
			}
		}
		return this;
	}
    //#endregion
    
    //#region 返回xml对象
    public IViewHandler getKernel() {
    	return confXml;
    }
    //#endregion
    
    //#region parsePropertys(IViewHandler configXml) 添加并解析基础配置信息
    /**
     * 添加并解析基础配置信息
     * @param configXml   xml配置信息对象
     * @throws Exception  异常
     * 2022年10月20日
     * @author mbg
     */
    public void parsePropertys(IViewHandler configXml) throws Exception {
    	if(configXml==null || confSourceList.contains(configXml)) {
    		return;
    	}
    	if(configXml.hasChildNodeByNodeName("root")) {
    		configXml = configXml.getFirstChildNodeByNodeName("root");
    	}
    	confSourceList.add(configXml);
    	parsePropertys(configXml,null);
    }
    //#endregion
    
    //#region parsePropertys(InputStream is,String filePath)  解析配置文件
	/**
	 * 解析配置文件
	 * @param is         读入流
	 * @throws Exception 异常
	 * 2014年4月19日
	 * @author 马宝刚
	 */
	public void parsePropertys(InputStream is,String filePath) throws Exception {
		if(is==null) {
			return;
		}
		// 通过读入流创建xml信息类
        IViewHandler vh = createViewHandler(is,filePath);
        confSourceList.add(vh);
    	parsePropertys(vh,filePath);
	}
	//#endregion
	
	//#region parsePropertys(String filePath)  解析配置文件
	/**
	 * 解析配置文件
	 * @param filePath          配置文件全路径
	 * @throws Exception        异常
	 * 2014年4月19日
	 * @author 马宝刚
	 */
	public void parsePropertys(String filePath) throws Exception {
		IViewHandler vh = createViewHandler(filePath);
		if(vh.isEmpty()) {
			return;
		}
		confSourceList.add(filePath);
		parsePropertys(vh,filePath);
	}
	//#endregion

	//#region getFixParameter 传入参数值，判断是否为参数变量，需要获取真正的参数值
	/**
	 * 传入参数值，判断是否为参数变量，需要获取真正的参数值
	 * 
	 * 比如参数值为： @paravalue 其中 paravalue 为参数变量
	 * 比如参数值为： @paravalue@default_value  其中 paravalue为参数变量，如果参数值为空，则返回 default_value
	 * 
	 * @author 刘虻
	 * 2007-9-15下午04:09:43
	 * @param parameter 整理前的参数值
	 * @return 整理后的参数值
	 */
    public String getFixParameter(String parameter) {
		//整理参数
		if(parameter==null) {
			parameter = "";
		}
		if (parameter.indexOf("@")==0) {
			//去掉开头的标识
			parameter = parameter.substring(1);
			//再检查是否有默认值标识符
			//（除了开头的@，变量末尾还有一个@，在这个@后面是默认值）
			int point = parameter.indexOf("@");
			if(point>0) {
				//存在默认值
				String defValue = parameter.substring(point+1);
				parameter = parameter.substring(0,point);
				
				//尝试获取变量值
				String res = getParameter(parameter);
				if(res.length()<1) {
					res = defValue;
				}
				return res;
			}else {
				//从全局配置文件中获取值
				return getParameter(parameter);
			}
		}
		return parameter;
	}
    //#endregion

	//#region hasParameter 返回是否存在指定参数值
    /**
     * 返回是否存在指定参数值
     * @param key  参数主键
     * @return     是否存在指定参数值
     * 2022年10月20日
     * @author mbg
     */
    public boolean hasParameter(String key) {
		int point = key.indexOf("/"); //参数分割点
		if(point==0) {
			key = key.substring(1); // 去掉开头的斜杠，如果存在的话
		}
		IViewHandler xml    = confXml; // 初始参数容器
		String       subKey = key;     // 参数段值
		while(point>0) {
			// 分级参数，即： key1/key2/key3
			subKey = key.substring(0,point); //获取配置信息主键
			key = key.substring(point+1);  //处理剩下的主键信息
			if(!xml.hasChildNodeByNodeName(subKey)) {
				return false;
			}
			xml = xml.getFirstChildNodeByNodeName(subKey);
		}
		return xml.hasChildNodeByNodeName(key);
	}
    //#endregion
	
    //#region getParameterXml 获取配置文件中指定段的XML对象
	/**
	 * 获取配置文件中指定段的XML对象
	 * @param key 节点名
	 * @return 节点对象
	 * 2014年5月5日
	 * @author 马宝刚
	 */
    public IViewHandler getParameterXml(String key) {
	    checkModified(); //检测文件是否发生变化 
	    if(key==null || key.length()<1) {
	        return confXml;
	    }
		int point = key.indexOf("/"); //参数分割点
		if(point==0) {
			key = key.substring(1); // 去掉开头的斜杠，如果存在的话
		}
		IViewHandler xml    = confXml; // 初始参数容器
		String       subKey = key;     // 参数段值
		while(point>0) {
			// 分级参数，即： key1/key2/key3
			subKey = key.substring(0,point); //获取配置信息主键
			key = key.substring(point+1);  //处理剩下的主键信息
			point = key.indexOf("/");
			
			xml = xml.getFirstChildNodeByNodeName(subKey);
		}
		return xml.getFirstChildNodeByNodeName(key);
    }
    //#endregion
    
    //#region getParamterAttribute 获取指定配置信息节点的指定属性值
    /**
     * 获取指定配置信息节点的指定属性值
     * @param key            节点名
     * @param attributeName  节点中的属性名
     * @return               对应的属性值
     * 2020年8月5日
     * @author MBG
     */
    public String getParamterAttribute(String key,String attributeName) {
    	return getParameterXml(key).getAttribute(attributeName);
    }
    //#endregion
	
    //#region getParameterValueMap 获取指定参数块
	/**
	 * 获取指定参数块
	 * 
	 * 配置文件中的参数块：
	 * 
	 * <参数块主键>
	 *     <参数1主键>参数1值</参数1主键>
	 *     <参数2主键>参数2值</参数2主键>
	 * </参数块主键>
	 * 
	 * @param key 参数主键
	 * @return 参数块容器
	 * 2014年4月19日
	 * @author 马宝刚
	 */
    public Map<String,Object> getParameterValueMap(String key) {
    	IViewHandler xml;
    	if(key==null || key.length()<1) {
    		xml = confXml;
    	}else {
    		xml = getParameterXml(key);
    	}
    	// 构建返回值
    	Map<String,Object> reMap = new HashMap<>();
    	// 获取一级子节点序列
    	List<IViewHandler> cList = xml.getChildDealNodes();
    	for(IViewHandler ele:cList) {
    		if(ele.hasChildNodes()) {
    			reMap.put(ele.getNodeName(),ele);
    		}else {
    			reMap.put(ele.getNodeName(),ele.getNodeText());
    		}
    	}
	    return reMap;
	}
    //#endregion

    //#region checkModified 【内部方法】判断配置文件是否发生变化
	/**
	 * 判断配置文件是否发生变化
	 * @author 刘虻
	 * 2009-2-26下午01:39:00
	 */
	private void checkModified() {
		boolean hasChanged = false;  //配置文件是否发生变化
		File    confFile;            //配置文件对象
		Long    time;                //最后更新时间
		String  path;
		
		// 带时间戳的文件路径
		Iterator<String> paths = confFileTimeMap.keySet().iterator();
		while(paths.hasNext()) {
			path = paths.next();
			if(!confSourceList.contains(path)) {
				confFileTimeMap.remove(path);
				continue;
			}
			time = confFileTimeMap.get(path);
			if(time==null) {
				continue;
			}
			confFile = new File(path);
			if(!confFile.exists()) {
				hasChanged = true;
				confSourceList.remove(path);
				confFileTimeMap.remove(path);
			}
			if(time!=confFile.lastModified()) {
				hasChanged = true;
				break;
			}
		}
		if(hasChanged) {
			try {
				reload();
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
	}
	//#endregion

	//#region createViewHandler(InputStream is,String filePath) 【内部方法】通过文件读入流，创建xml信息类
	/**
	 * 通过文件读入流，创建xml信息类
	 * @param is          读入流
	 * @param filePath    对应的文件路径
	 * @return            xml信息类
	 * @throws Exception  异常
	 * 2022年10月20日
	 * @author mbg
	 */
	private INodeHandler createViewHandler(InputStream is,String filePath) throws Exception {
		INodeHandler configXml = FNodeHandler.newNodeHandler("UTF-8"); //构建新的xml处理类
    	configXml.setXmlStyle(true);
    	try {
    		//Xml配置文件
    		((INodeHandler)configXml).setStream(is,filePath);
    		
        	if(configXml.hasChildNodeByNodeName("root")) {
        		configXml = (INodeHandler)configXml.getFirstChildNodeByNodeName("root");
        	}
    	}catch(Exception e) {
    		e.printStackTrace();
    		throw new BeanException("Load Config File Exception Path:["+filePath+"]");
    	}
    	return configXml;
	}
	//#endregion

	//#region createViewHandler(String filePath) 【内部方法】通过文件路径创建xml信息类
	/**
	 * 通过文件路径创建xml信息类
	 * @param filePath    文件路径
	 * @return            对应的xml信息类
	 * @throws Exception  异常
	 * 2022年10月20日
	 * @author mbg
	 */
	private INodeHandler createViewHandler(String filePath) throws Exception {
		File         confFile;         // 配置文件对象
		INodeHandler reNh      = null; // 构建返回值
        try {
        	if(filePath.startsWith("!")) {
        		filePath = filePath.substring(1);
        		//内部资源
        		String baseClassPath = SFilesUtil.getBaseClassPath(XmlConfProp.class);
        		baseClassPath = SFilesUtil.getAllFilePath("..",baseClassPath); //无论返回 /WEB-INF/lib  还是 /WEB-INF/classes
        		
        		//先判断外部是否有同路径同名文件，如果有，则取外部文件
        		confFile = new File(SFilesUtil.getAllFilePath(filePath,baseClassPath));
        		if(confFile.exists()) {
        			confFileTimeMap.put(filePath,confFile.lastModified());
        			reNh = FNodeHandler.newFile(confFile,"UTF-8");
        		}else {
        			//内部资源加载器
        			ResourcesLoader rl = _beanFactory.getObject(ResourcesLoader.class,this);
        			reNh = createViewHandler(rl.getFile(filePath),filePath);
        		}
        	} else {
        		confFile = new File(filePath);
        		if(!confFile.exists()) {
        			throw new BeanException("The Config File:["+filePath+"] Not Found");
        		}else {
            		confFileTimeMap.put(filePath,confFile.lastModified());
            		reNh = FNodeHandler.newFile(confFile,"UTF-8");
        		}
        	}
        }catch(Exception e) {
            e.printStackTrace();
            throw new BeanException("Load Config File Exception Path:["+filePath+"]");
        }
        if(reNh!=null && reNh.hasChildNodeByNodeName("root")) {
        	reNh = (INodeHandler)reNh.getFirstChildNodeByNodeName("root");
        }
        return reNh;
	}
	//#endregion

    //#region parsePropertys 【内部方法】解析配置文件
    /**
     * 解析配置文件
     * @param configXml   配置信息xml对象
     * @throws Exception  异常
     * 2021年9月12日
     * @author MBG
     */
    private void parsePropertys(IViewHandler configXml,String filePath) throws Exception {
    	if(configXml==null || configXml.isEmpty()) {
    		return;
    	}
    	confXml.merge(configXml);
    	String nodeValue; //节点值
    	List<IViewHandler> cXmls = configXml.getChildNodesByNodeName("import");
    	INodeHandler cNh;
    	for(IViewHandler cXml:cXmls) {
			nodeValue = cXml.nt(); //获取节点值
			cNh = createViewHandler(nodeValue);
			if(cNh.isEmpty()) {
				continue;
			}
			if(filePath!=null) {
				filePath += "@"+nodeValue;
				confSourceList.add(filePath);
			}
			parsePropertys(cNh,filePath);
    	}
    }
    //#endregion
	
}
